#include "plugins.h"
#include "error.h"
#include "prefix.h"
#include "dict.h"
#include <string.h>
#ifndef WIN32
#include <dlfcn.h>
#else
#include <ltdl.h>
#define dlopen(a, b) lt_dlopen(a)
#define dlerror(string) lt_dlerror(string)
#define dlsym(a, b) lt_dlsym(a, b)
#define dlclose(a) lt_dlclose(a)
#endif

char *plgn_syms[] = { "___init", "___ugly", "___end", "___cut",
	"___copy", "___paste", "___open", "___save",
	"___info", "___author", "___journChange",
	"___focus", "___tab", "___delete", "___back",
	"___fwrd", "___undo", "___redo", "___srch",
	"___res", "___search"
};

//please note that the ___srch is for menubar searches
//not searches in the search tab.

int plgn_count = 21; //one plus last sym number!

struct elog_plgn_lib_stat {
	void *lib;
	struct dict *stat;
	void *symbols[24];	//an array of functions
	//note, this is not null terminated, it's not terminated
	int exists[24];		//whether or not the symbols exist
};

struct elog_plgn_lib {
	struct elog_plgn_lib_stat lib;
	char *name;
	struct dict *instance;
};


struct elog_plgn_lib _plgn_libs[32];
int _plgn_last = 0;

int plgn_page = 2;
int elog_plgn_page()
{
	return plgn_page++;
}

int elog_plgn_page_decrement()
{
	return --plgn_page;
}

char *_plgn_datadir;


void __copy_lib(struct elog_plgn_lib_stat *old, 
			struct elog_plgn_lib_stat *new)
{
	new->lib = old->lib;
	new->stat = old->stat;
	int c;
	for (c=0; c < plgn_count; ++c) {
		new->symbols[c] = old->symbols[c];
		new->exists[c] = old->exists[c]; 	
	}
	
} 

void *__load_plgn(const char *name)
{
	void *ptr;
	char *plgn_loc;
	elog_sp_cat(&plgn_loc, getenv("HOME"), "/", _ELOG_CONFIG,
		    "/modules/", name, NULL);

	ptr = dlopen(plgn_loc, RTLD_LAZY);

	free(plgn_loc);
	if (!(ptr)) {		//if not in home, check in system
		elog_sp_cat(&plgn_loc, DATADIR, "/ejourn/modules/", name,
			    NULL);

		ptr = dlopen(plgn_loc, RTLD_LAZY);

		free(plgn_loc);
	}

	if (!(ptr)) {
		elog_err_print_console("Error:");
		elog_err_print_console(dlerror());
		elog_err_print("\nFailed to load plugin!\n");
		return NULL;
	}
	return ptr;
}

int elog_plgn_open(const char *name)
{
	int found = 0;
	int c; //sharing other library if loaded multiple times.
	for (c=0; c < _plgn_last; ++c) {
		if (strcmp(_plgn_libs[c].name, name) == 0) {
			__copy_lib(&(_plgn_libs[c].lib), &(_plgn_libs[_plgn_last].lib));
			_plgn_libs[_plgn_last].name = _plgn_libs[c].name;
			found = 1;
			break;	
		}	
	}
	if (!found) { //creating new lib if first load.
		_plgn_libs[_plgn_last].lib.lib = __load_plgn(name);
		elog_sp_cat(&(_plgn_libs[_plgn_last].name), name, NULL);
		_plgn_libs[_plgn_last].lib.stat = dict_new();
	}
	
	//instance data.
	_plgn_libs[_plgn_last].instance = dict_new();
	
	if (!found) {
		for (c = 0; c < plgn_count; ++c) {
			dlerror();
			/* now locate the 'readfile' function in the library */
			_plgn_libs[_plgn_last].lib.symbols[c] =
			    dlsym(_plgn_libs[_plgn_last].lib.lib, plgn_syms[c]);
	
			/* check that no error occured */
			const char *error_msg = dlerror();
			if (!error_msg)
				_plgn_libs[_plgn_last].lib.exists[c] = 1;
			else
				_plgn_libs[_plgn_last].lib.exists[c] = 0;
		}
	}

	++_plgn_last;
	elog_plgn_call(name, "___init", (void *) name);

	return 0;
}


int elog_plgn_eff_call(const char *plgn, int func, void *dat)
{
	void *ptr;
	int c;
	for (c = 0; c < _plgn_last; ++c)
		if (strcmp(plgn, _plgn_libs[c].name) == 0)
			break;

	if (c == _plgn_last) {
		elog_err_print_console("\nCouldn't find plugin:");
		elog_err_print_console(plgn);
		elog_err_print_console("\n");
		return 1;
	}
	ptr = _plgn_libs[c].lib.lib;


	int (*readfile) (void *, void *, void *);	//our functor
	readfile = _plgn_libs[c].lib.symbols[func];
	if (_plgn_libs[c].lib.exists[func])
		(*readfile) (_plgn_libs[c].lib.stat, _plgn_libs[c].instance, dat);
	return 0;
}
int elog_plgn_call(const char *plgn, const char *func, void *dat)
{
	//void *ptr;
	int c;
	for (c = 0; c < _plgn_last; ++c)
		if (strcmp(plgn, _plgn_libs[c].name) == 0)
			break;

	if (c == _plgn_last) {
		elog_err_print_console("\nCouldn't find plugin:");
		elog_err_print_console(plgn);
		elog_err_print_console("\n");
		return 1;
	}
	//ptr = _plgn_libs[c].lib.lib;


	int (*readfile) (void *, void *, void *);	//our functor


	int i;
	for (i = 0; i < plgn_count; ++i)
		if (strcmp(func, plgn_syms[i]) == 0)
			break;
	readfile = _plgn_libs[c].lib.symbols[i];
	if (_plgn_libs[c].lib.exists[i])
		(*readfile) (_plgn_libs[c].lib.stat, _plgn_libs[c].instance, dat);

	return 0;
}
void elog_plgn_call_all(const char *func, void *dat)
{
	int n;
	for (n = 0; n < plgn_count; ++n)	//search for func num
		if (strcmp(plgn_syms[n], func) == 0)
			break;

	elog_plgn_eff_call_all(n, dat);
}
void elog_plgn_eff_call_all(int func, void *dat)
{
	int (*readfile) (void *);

	int c;
	for (c = 0; c < _plgn_last; ++c) {
		readfile = _plgn_libs[c].lib.symbols[func];

		if (_plgn_libs[c].lib.exists[func]) 
			(*readfile) (dat);
	}
}


int elog_plgn_close(const char *name)
{
	elog_plgn_call(name, "___end", NULL);
	void *ptr;
	int c;
	for (c = 0; c < _plgn_last; ++c)
		if (strcmp(name, _plgn_libs[c].name) == 0)
			break;
	ptr = _plgn_libs[c].lib.lib;

	if (dlclose(ptr))	//error
		elog_err_print_console("Failed to close plugin\n");



	free(_plgn_libs[c].name);
	_plgn_libs[c] = _plgn_libs[_plgn_last - 1];

	--_plgn_last;
	return 0;
}

void elog_plgn_cleanup()
{
	elog_plgn_call_all("___ugly", NULL);
	int c;
	for (c = 0; c < _plgn_last; ++c) {
		if (dlclose(_plgn_libs[c].lib.lib))	//error
		{
			elog_err_print_console("Failed to close plugin\n");
			elog_err_print_console(_plgn_libs[c].name);
		}
		free(_plgn_libs[c].name);
	}
	_plgn_last = 0;
}

char **elog_plgn_list()
{
	char *path_sys;
	elog_sp_cat(&path_sys, DATADIR, "/ejourn/modules/", NULL);
	char **sys = elog_io_dirContents(path_sys);

	char *path_home;
	elog_sp_cat(&path_home, getenv("HOME"), "/", _ELOG_CONFIG,
		    "/modules/", NULL);
	char **home = elog_io_dirContents(path_home);


	int sys_size;
	for (sys_size = 0; sys[sys_size] != NULL; ++sys_size);

	int home_size;
	for (home_size = 0; home[home_size] != NULL; ++home_size);

	char **list = malloc((sizeof *list) * (home_size + sys_size + 1));

	int c;
	for (c = 0; c < home_size; ++c)
		elog_sp_cat(&(list[c]), home[c], NULL);
	for (; c - home_size < sys_size; ++c) {
		int found = 0;
		int j;
		for (j = 0; j < home_size; ++j)
			if (strcmp(sys[c - home_size], home[j]) == 0)
				found = 1;
		if (!found)
			elog_sp_cat(&(list[c]), sys[c - home_size], NULL);

	}

	list[c] = NULL;

	free(path_sys);
	free(path_home);
	elog_sp_ArrFree(sys);
	elog_sp_ArrFree(home);

	return list;
}
char *__auth_info(const char *name, int symbol)
{
	int c;
	for (c = 0; c < _plgn_last; ++c)
		if (strcmp(name, _plgn_libs[c].name) == 0)
			break;

	if (c == _plgn_last) {
		void *ptr = __load_plgn(name);

		if (!ptr) {
			elog_err_print_console("\nCouldn't find plugin:");
			elog_err_print_console(name);
			elog_err_print_console("\n");
			return NULL;
		}

		/* first define a function pointer variable to hold the function's address */
		int (*readfile) (void);
		/* then define a pointer to a possible error string */
		const char *error_msg;
		/* finally, define a pointer to the returned file */


		dlerror();

		/* now locate the 'readfile' function in the library */

		readfile = dlsym(ptr, plgn_syms[symbol]);

		/* check that no error occured */
		error_msg = dlerror();
		if (error_msg) {
			elog_err_print_console
			    ("Error locating running function:\n");
			elog_err_print_console("___info");
			elog_err_print_console("\n");
			return NULL;
		}

		/* finally, call the function, with a given file path */
		char *ret;
		elog_sp_cat(&ret, (*readfile) (), NULL);

		dlclose(ptr);

		return ret;
	} else {
		int (*readfile) (void);
		readfile = _plgn_libs[c].lib.symbols[symbol];
		char *ret = NULL;;
		elog_sp_cat(&ret, (*readfile) (), NULL);
		return ret;
	}
	return NULL;
}
char *elog_plgn_info(const char *name)
{
	return __auth_info(name, ELOG_PLGN_SYM_INFO);
}
char *elog_plgn_author(const char *name)
{
	return __auth_info(name, ELOG_PLGN_SYM_AUTHOR);
}

int elog_plgn_check(const char *name)
{
	int c;
	for (c = 0; c < _plgn_last; ++c)
		if (strcmp(_plgn_libs[c].name, name) == 0)
			return 1;
	return 0;
}
const char *elog_plgn_prefix_DATADIR()
{
	return _plgn_datadir;
}
void elog_plgn_initialize(const char *fileName)
{
	/*First, initialize the needed folder locs */
	elog_sp_cat(&_plgn_datadir, DATADIR, NULL);

	/*Now load plugins: */
	struct elog_io_file *f =
	    elog_io_initialize(fileName, ELOG_IO_READ);
	if (f == NULL)
		return;

	char *line;
	while ((line = elog_io_read_realLine(f))) {
		if (strlen(line) < 2)
			break;
		elog_plgn_open(line);
		free(line);
	}
	elog_io_close(f);

}
void elog_plgn_save(const char *fileName)
{
	struct elog_io_file *f =
	    elog_io_initialize(fileName, ELOG_IO_WRITE);
	int c;
	for (c = 0; c < _plgn_last; ++c) {
		 if (_plgn_libs[c].lib.lib) { //don't save unloaded plugins
			  elog_io_writeLine(f, _plgn_libs[c].name, 0);
			  elog_io_writeLine(f, "\n", 0);
		 }
	}
	elog_io_close(f);
}
int elog_plgn_has(int func, const char *plgn)
{
	int c;
	for (c = 0; c < _plgn_last; ++c)
		if (strcmp(plgn, _plgn_libs[c].name) == 0)
			break;
	if (c == _plgn_last)
		return 0;


	if (_plgn_libs[c].lib.exists[func])
		return 1;
	return 0;
}
